use core::slice;
use objc::{sel, sel_impl};
use std::ffi::{c_char, c_void};

use objc_derive::{selector_export, Alloc, Deref, GetObject, Message};

pub trait ObjCObject {
  fn objc_object(&self) -> id;
}

#[allow(non_camel_case_types)]
pub type id = *mut objc::runtime::Object;

pub type NSInteger = i64;
pub type NSUInteger = u64;

extern "C" {
  pub fn NSClassFromString(clazz: NSString) -> Class;
}

#[repr(transparent)]
#[derive(Clone, Message, GetObject, Deref, Alloc)]
pub struct NSString(pub id);

impl From<&str> for NSString {
  fn from(val: &str) -> Self {
    NSString::alloc().init_with_bytes_length_encoding(val.as_ptr() as *const c_void, val.len(), 4)
  }
}

impl From<&NSString> for &str {
  fn from(value: &NSString) -> Self {
    unsafe {
      let bytes = value.utf8_string() as *const u8;
      let bytes = slice::from_raw_parts(bytes, value.length_of_bytes_using_encoding(4));
      std::str::from_utf8(bytes).unwrap()
    }
  }
}

impl std::fmt::Display for NSString {
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    let s: &str = self.into();
    write!(f, "{}", s)
  }
}

pub type NSStringEncoding = NSUInteger;
impl NSString {
  #[selector_export("initWithBytes:length:encoding:")]
  pub fn init_with_bytes_length_encoding(
    &self,
    bytes: *const c_void,
    length: usize,
    encoding: NSStringEncoding,
  ) -> NSString;

  #[selector_export("lengthOfBytesUsingEncoding:")]
  pub fn length_of_bytes_using_encoding(&self, encoding: NSStringEncoding) -> usize;

  #[selector_export("UTF8String")]
  pub fn utf8_string(&self) -> *const c_char;
}

#[repr(transparent)]
#[derive(Clone, Message, GetObject, Alloc, Deref)]
pub struct Class(pub id);

#[repr(transparent)]
#[derive(Clone, Message, GetObject, Alloc, Deref)]
pub struct NSArray(pub id);

impl<T, const N: usize> From<&[T; N]> for NSArray
where
  T: ObjCObject,
{
  fn from(value: &[T; N]) -> Self {
    NSArray::array_with_objects_count(value.as_ptr() as usize as *mut id, value.len() as u64)
  }
}

impl NSArray {
  pub fn from_slice(slice: &[id]) -> Self {
    let raw_ptr = slice.as_ptr();
    let ptr_value = raw_ptr as usize;
    let mut_ptr = ptr_value as *mut id;

    NSArray::array_with_objects_count(mut_ptr, slice.len() as u64)
  }

  pub fn map<T, F: Fn(id) -> T>(&self, transform: F) -> Vec<T> {
    let count = self.count();

    (0..count)
      .map(|index| {
        let item: id = self.object_at_index(index);
        transform(item)
      })
      .collect()
  }
}

impl NSArray {
  #[selector_export("count")]
  pub fn count(&self) -> usize;

  #[selector_export(NSArray, "arrayWithObjects:count:")]
  pub fn array_with_objects_count(objects: *mut id, count: u64) -> NSArray;

  #[selector_export("objectAtIndex:")]
  pub fn object_at_index(&self, index: usize) -> id;
}

#[repr(transparent)]
#[derive(Clone, Message, GetObject, Alloc, Deref)]
pub struct NSDictionary(pub id);

impl NSDictionary {
  #[selector_export("init")]
  pub fn init(&self) -> NSDictionary;
}

#[repr(transparent)]
#[derive(Clone, Message, GetObject, Alloc, Deref)]
pub struct NSURL(pub id);

impl NSURL {
  #[selector_export(NSURL, "URLWithString:")]
  pub fn url_with_string(url_string: NSString) -> NSURL;

  #[selector_export(NSURL, "fileURLWithPath:")]
  pub fn file_url_with_path(path: NSString) -> NSURL;

  #[selector_export("absoluteString")]
  pub fn absolute_string(&self) -> NSString;

  #[selector_export("path")]
  pub fn path(&self) -> NSString;

  #[selector_export("startAccessingSecurityScopedResource")]
  pub fn start_accessing_security_scoped_resource(&self) -> bool;
}

#[repr(transparent)]
#[derive(Clone, Message, GetObject, Alloc, Deref)]
pub struct NSData(pub id);

impl NSData {
  #[selector_export(NSData, "dataWithBytes:length:")]
  pub fn data_with_bytes_length(bytes: &[u8], length: usize) -> NSData;
}
